#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<queue>

#define RECVSIZE 1024

using namespace boost;

class MsgNode//负责封装数据
{
public:
    MsgNode(size_t total_len):msg_(nullptr),total_len_(total_len),curr_len_(0){};//实例化读
    MsgNode(const char *msg,size_t total_len):total_len_(total_len),curr_len_(0){
        msg_ = new char[total_len];
        memcpy(msg_,msg,total_len);
    };//实例化写

    ~MsgNode(){delete[]msg_;};

private:
    friend class Session;
    char *msg_;
    size_t total_len_;
    size_t curr_len_;

};

//一个类操作会话层
class Session //负责读写数据
{
public:
    Session(std::shared_ptr<asio::ip::tcp::socket> socket):socket_(socket),send_pending_(false)
    {std::cout << "Session constructor." << std::endl;};

    void Connect(asio::ip::tcp::endpoint &ep){ socket_->connect(ep);};


    //这种操作不能投入实际应用
    /*
        因为回调函数返回时不代表数据发送完毕，所以我们在回调函数中进行判断并再次递归
        但是当我们刚进入递归还未开始async_write_some时，WriteToSocketErr如果再次被调用，
        由于是IO多路复用的模型，所以会导致发送数据异常，为了解决这个问题，可以使用队列缓存要发送的数据，
        使用一个flag判断是否发送完毕
    */
    void WriteCallBackErr(const boost::system::error_code & ec, std::size_t bytes_transferred,
    std::shared_ptr<MsgNode> msg_node);//写回调函数
    void WriteToSocketErr(const std::string& buf);//写操作

    void WriteCallBack(const boost::system::error_code & ec, std::size_t bytes_transferred,
    std::shared_ptr<MsgNode> msg_node);//写回调函数
    void WriteToSocket(const std::string& buf);//写操作

    void WriteAllToSocket(const std::string& buf);
    void WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);


    void ReadFromSocket();//读操作
    void ReadCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);//读操作回调函数

    void ReadAllFromSocket(const std::string& buf);//基于async_receive
    void ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);
    ~Session(){};

private:
    std::shared_ptr<asio::ip::tcp::socket> socket_;
    std::shared_ptr<MsgNode> msg_node_;//存储的数据
    std::shared_ptr<MsgNode> _recv_node;

    
    std::queue<std::shared_ptr<MsgNode>>msg_queue_;
    bool send_pending_;//发送完毕flag
    bool _recv_pending;
};

void Session::WriteCallBackErr(const boost::system::error_code & ec, std::size_t bytes_transferred,
    std::shared_ptr<MsgNode> msg_node)//写回调函数
{
    if (ec.value() != 0)
    {
        std::cout << "函数：" << __func__ <<  "第" <<__LINE__<< "行" << "报错" << std::endl;
        return;
    }
    msg_node->curr_len_ += bytes_transferred;
    if (msg_node->curr_len_ < msg_node->total_len_)
    {
        //还未发完，需要递归自己继续发送
        this->socket_->async_write_some(
        asio::buffer(msg_node->msg_ + msg_node->curr_len_,msg_node->total_len_ - msg_node->curr_len_),
        //该回调函数参数入口为2，但是我们自己封装的回调函数参数入口为3，所以使用bind更改一下
        std::bind(&Session::WriteCallBackErr,this,std::placeholders::_1,std::placeholders::_2,msg_node));
    }
}

void Session::WriteToSocketErr(const std::string& buf)//写操作
{
    msg_node_ = std::make_shared<MsgNode>(buf.c_str(),buf.length());
    
    this->socket_->async_write_some(
        asio::buffer(msg_node_->msg_,msg_node_->total_len_),
        //该回调函数参数入口为2，但是我们自己封装的回调函数参数入口为3，所以使用bind更改一下
        std::bind(&Session::WriteCallBackErr,this,std::placeholders::_1,std::placeholders::_2,msg_node_));
}

/************************************改进***********************************/
void Session::WriteCallBack(const boost::system::error_code & ec, std::size_t bytes_transferred,
    std::shared_ptr<MsgNode> msg_node)//写回调函数
{
    if (ec.value() != 0)
    {
        std::cout << "函数：" << __func__ <<  "第" <<__LINE__<< "行" << "报错" << std::endl;
        return;
    }
   //取出队首元素即当前未发送完数据
    auto & send_data = msg_queue_.front();
    send_data->curr_len_ += bytes_transferred;
    //数据未发送完， 则继续发送
    if (send_data->curr_len_ < send_data->total_len_) {
        this->socket_->async_write_some(asio::buffer(send_data->msg_ + send_data->curr_len_, send_data->total_len_-send_data->curr_len_),
            std::bind(&Session::WriteCallBack,
            this, std::placeholders::_1, std::placeholders::_2,send_data));
        return;
    }
    //如果发送完，则pop出队首元素
    msg_queue_.pop();
    //如果队列为空，则说明所有数据都发送完,将pending设置为false
    if (msg_queue_.empty()) {
        send_pending_ = false;
    }
    //如果队列不是空，则继续将队首元素发送
    if (!msg_queue_.empty()) {
        auto& send_data = msg_queue_.front();
        this->socket_->async_write_some(asio::buffer(send_data->msg_ + send_data->curr_len_, send_data->total_len_ - send_data->curr_len_),
            std::bind(&Session::WriteCallBack,
                this, std::placeholders::_1, std::placeholders::_2,send_data));
    }
}

void Session::WriteToSocket(const std::string& buf)//写操作
{

    /*使用new 手动调用构造函数   或者   使用make_shared 构造对象指针*/
    //msg_queue_.emplace(new MsgNode(buf.c_str(), buf.length()));//入队  
    msg_queue_.emplace(std::make_shared<MsgNode>(buf.c_str(),buf.length()));//入队
    
    auto msg_node = msg_queue_.front();

    if (send_pending_)
    {
        std::cout << "上次数据还未发送完毕" << std::endl;
        return ;
    }
    
    this->socket_->async_write_some(
        asio::buffer(buf),
        //该回调函数参数入口为2，但是我们自己封装的回调函数参数入口为3，所以使用bind更改一下
        std::bind(&Session::WriteCallBack,this,std::placeholders::_1,std::placeholders::_2,msg_node));

    send_pending_ = true;
}

/**********************************************再次改进**************************************************/
/*
async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度，这样我们每次都要在回调函数判断发送数据是否完成，
asio提供了一个更简单的发送函数async_send，这个函数在发送的长度未达到我们要求的长度时就不会触发回调，
所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送，
所以async_send不能和async_write_some混合使用，我们基于async_send封装另外一个发送函数
*/

//不能与async_write_some混合使用
void Session::WriteAllToSocket(const std::string& buf) {
    //插入发送队列
    msg_queue_.emplace(new MsgNode(buf.c_str(), buf.length()));
    //pending状态说明上一次有未发送完的数据
    if (send_pending_) {
        return;
    }
    //异步发送数据，因为异步所以不会一下发送完
    this->socket_->async_send(asio::buffer(buf), 
        std::bind(&Session::WriteAllCallBack, this,
            std::placeholders::_1, std::placeholders::_2));
    send_pending_ = true;
}

void Session::WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred){
    if (ec.value() != 0) {
        std::cout << "Error occured! Error code = "
            << ec.value()
            << ". Message: " << ec.message();
        return;
    }
    //如果发送完，则pop出队首元素
    msg_queue_.pop();
    //如果队列为空，则说明所有数据都发送完,将pending设置为false
    if (msg_queue_.empty()) {
        send_pending_ = false;
    }
    //如果队列不是空，则继续将队首元素发送
    if (!msg_queue_.empty()) {
        auto& send_data = msg_queue_.front();
        this->socket_->async_send(asio::buffer(send_data->msg_ + send_data->curr_len_, send_data->total_len_ - send_data->curr_len_),
            std::bind(&Session::WriteAllCallBack,
                this, std::placeholders::_1, std::placeholders::_2));
    }
}



/***************************************************读操作*****************************************************/
//不考虑粘包情况， 先用固定的字节接收
void Session::ReadFromSocket() {
    if (_recv_pending) {
        return;
    }
    //可以调用构造函数直接构造，但不可用已经构造好的智能指针赋值
    /*auto _recv_nodez = std::make_unique<MsgNode>(RECVSIZE);
    _recv_node = _recv_nodez;*/
    _recv_node = std::make_shared<MsgNode>(RECVSIZE);
    socket_->async_read_some(asio::buffer(_recv_node->msg_, _recv_node->total_len_), std::bind(&Session::ReadCallBack, this,
        std::placeholders::_1, std::placeholders::_2));
    _recv_pending = true;
}


void Session::ReadCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred){
    _recv_node->curr_len_ += bytes_transferred;
    //没读完继续读
    if (_recv_node->curr_len_ < _recv_node->total_len_) {
        socket_->async_read_some(asio::buffer(_recv_node->msg_+_recv_node->curr_len_,
            _recv_node->total_len_ - _recv_node->curr_len_), std::bind(&Session::ReadCallBack, this,
            std::placeholders::_1, std::placeholders::_2));
        return;
    }
    //将数据投递到队列里交给逻辑线程处理，此处略去
    //如果读完了则将标记置为false
    _recv_pending = false;
    //指针置空
    _recv_node = nullptr;    
}

void Session::ReadAllFromSocket(const std::string& buf) {
    if (_recv_pending) {
        return;
    }
    //可以调用构造函数直接构造，但不可用已经构造好的智能指针赋值
    /*auto _recv_nodez = std::make_unique<MsgNode>(RECVSIZE);
    _recv_node = _recv_nodez;*/
    _recv_node = std::make_shared<MsgNode>(RECVSIZE);
    socket_->async_receive(asio::buffer(_recv_node->msg_, _recv_node->total_len_), std::bind(&Session::ReadAllCallBack, this,
        std::placeholders::_1, std::placeholders::_2));
    _recv_pending = true;
}

void Session::ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred) {
    _recv_node->curr_len_ += bytes_transferred;
    //将数据投递到队列里交给逻辑线程处理，此处略去
    //如果读完了则将标记置为false
    _recv_pending = false;
    //指针置空
    _recv_node = nullptr;
}


int main(int argc, char const *argv[])
{
    /* code */
    return 0;
}
